%% -*- Erlang -*-
%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
%%-----------------------------------------------------------------------------
%% File        : hipe_ssa_const_prop.inc
%% Author      : Kostis Sagonas <kostis@it.uu.se>
%% Description : Supporting routines for sparse conditional constant
%%		 propagation on SSA form.
%%
%% Created     : 21 June 2004 by Kostis Sagonas <kostis@it.uu.se>
%%-----------------------------------------------------------------------------

%%-----------------------------------------------------------------------------
%% Procedure : propagate/1
%% Purpose   : Perform sparse conditional constant propagation on a
%%             control flow graph
%% Arguments : CFG  - The cfg to work on
%% Returns   : A new cfg.
%%-----------------------------------------------------------------------------

-spec propagate(#cfg{}) -> #cfg{}.

propagate(CFG) ->
  Environment    = create_env(CFG),
  StartEdge      = {?CFG:start_label(CFG), ?CFG:start_label(CFG)},
  NewEnvironment = scc([StartEdge], [], Environment),
  NewCFG         = update_cfg(NewEnvironment),
  NewCFG.

%%-----------------------------------------------------------------------------
%% Procedure : visit_expressions/2 & visit_expressions/4
%% Purpose   : visit each instruction in a list of instructions.
%% Arguments : Instructions - the list of instructions to visit
%%             Environment  - have a guess.
%%             FlowWork     - list of destination part of flowgraph edges 
%%                            from the visited instructions
%%             SSAWork      - resulting ssa-edges from visited instrs.
%% Returns   : {FlowWorkList, SSAWorkList, Environment} 
%%-----------------------------------------------------------------------------

visit_expressions(Instructions, Environment) ->
  visit_expressions(Instructions, Environment, [], []).

visit_expressions([], Environment, FlowWork, SSAWork) ->
  {FlowWork, SSAWork, Environment};
visit_expressions([Inst | Insts], Environment, FlowWork, SSAWork) ->
  {MoreFlowWork, MoreSSAWork, Environment1}
    = visit_expression(Inst, Environment),
  FlowWork1 = MoreFlowWork ++ FlowWork,
  SSAWork1  = MoreSSAWork  ++ SSAWork,
  visit_expressions(Insts, Environment1, FlowWork1, SSAWork1).

%%-----------------------------------------------------------------------------
%% The environment record: Shared between incarnations of SCCP.
%%-----------------------------------------------------------------------------

-record(env, {cfg                                 :: #cfg{},
	      executable_flags = gb_sets:empty()  :: gb_sets:set(),
	      handled_blocks   = gb_sets:empty()  :: gb_sets:set(),
	      lattice_values   = gb_trees:empty() :: gb_trees:tree(),
	      ssa_edges        = gb_trees:empty() :: gb_trees:tree()
	     }).

create_env(CFG) ->
  #env{cfg              = CFG,
       executable_flags = gb_sets:empty(),
       handled_blocks   = gb_sets:empty(),
       lattice_values   = initialize_lattice(CFG),
       ssa_edges        = initialize_ssa_edges(CFG)
      }.

env__cfg(#env{cfg=CFG}) -> CFG.
env__executable_flags(#env{executable_flags=Flags}) -> Flags.
env__lattice_values(#env{lattice_values=Values}) -> Values.
env__ssa_edges(#env{ssa_edges=Edges}) -> Edges.

%%-----------------------------------------------------------------------------
%% Procedure : initialize_lattice/1
%% Purpose   : Compute the initial value-lattice for the CFG
%% Arguments : CFG a control flow graph
%% Returns   : a value-latice (gb_tree)
%%-----------------------------------------------------------------------------

initialize_lattice(CFG) ->
  Lattice    = gb_trees:empty(),
  Parameters = ?CFG:params(CFG),
  Inserter   = fun(Parameter, Tree) ->
                   gb_trees:insert(Parameter, bottom, Tree)
	       end,
  lists:foldl(Inserter, Lattice, Parameters).

%%-----------------------------------------------------------------------------
%% Procedure : initialize_ssa_edges/1
%% Purpose   : Compute the SSA edges in the CFG. SSA edges are used to map
%%             the definition of a value to its uses.
%% Arguments : CFG - the cfg
%% Returns   : A gb_tree of values (variables & registers) to 
%%             lists of {Node, Instruction} pairs.
%%-----------------------------------------------------------------------------

initialize_ssa_edges(CFG) ->
  IterateNodes =
    fun(Node, Tree1) ->
        IterateInstructions =
          fun(Instruction, Tree2) ->
              IterateArguments =
                fun(Argument, Tree3) ->
                    Data    = gb_trees:lookup(Argument, Tree3),
                    NewEdge = {Node, Instruction},
                    case Data of
                      none ->
                        %% insert assumes key is not present
                        gb_trees:insert(Argument, [NewEdge], Tree3);
                      {value, EdgeList} ->
                        %% update assumes key is present
                        gb_trees:update(Argument, [NewEdge|EdgeList], Tree3)
                    end
                end,
              Arguments = ?CODE:uses(Instruction),
              lists:foldl(IterateArguments, Tree2, Arguments)
          end,
        Instructions = hipe_bb:code(?CFG:bb(CFG, Node)),
        lists:foldl(IterateInstructions, Tree1, Instructions)
    end,
  NodeList = ?CFG:labels(CFG),
  lists:foldl(IterateNodes, gb_trees:empty(), NodeList).

%%-----------------------------------------------------------------------------
%% Procedure : scc/3
%% Purpose   : Do the symbolic execution of a cfg and compute the resulting 
%%             value-lattice, and reachability information (Environment).
%%             This is the main loop that does a fixpoint computation of the 
%%             lattice-values for each variable and register.
%% Arguments : FlowWorkList - work list of control-flow edges
%%             SSAWorkList  - work list of ssa-edges
%%             Environment  - the environment that have been computed so far.
%% Returns   : The environment after execution 
%%-----------------------------------------------------------------------------

scc([], [], Environment) ->
  Environment;
%% Take an element from the FlowWorkList and process it
scc([{Source,Destination} | FlowWorkList], SSAWorkList, Environment) ->
  case executable({Source, Destination}, Environment) of
    true ->
      scc(FlowWorkList, SSAWorkList, Environment);
    false ->
      Environment1 = mark_as_executable({Source,Destination}, Environment),
      Code         = extract_code(Destination, Environment),
      {Environment2, Code1, ExtraSSA} =
        visit_phi_nodes(Code, Destination, Environment1, []),
      case handled(Destination, Environment2) of
        true ->
          scc(FlowWorkList, ExtraSSA ++ SSAWorkList, Environment2);
        false ->
          {MoreFlowDests, MoreSSAWork, Environment3} = 
            visit_expressions(Code1, Environment2),
          MoreFlowWork  = [{Destination, Node} || Node <- MoreFlowDests],
          FlowWorkList1 = MoreFlowWork ++ FlowWorkList,
          SSAWorkList1  = ExtraSSA ++ MoreSSAWork ++ SSAWorkList,
	  Environment4  = mark_as_handled(Destination, Environment3),
          scc(FlowWorkList1, SSAWorkList1, Environment4)
      end
  end;
%% Take an element from the SSAWorkList and process it
scc([], [{Node, Instruction} | SSAWorkList], Environment) ->
  case reachable(Node, Environment) of
    true ->
      case ?CODE:is_phi(Instruction) of
        true ->
          {Environment1, MoreSSA} = visit_phi(Instruction, Node, Environment),
          scc([], MoreSSA ++ SSAWorkList, Environment1);
        false ->
          {MoreFlowDests, MoreSSAWork, Environment1} = 
            visit_expression(Instruction, Environment),
          SSAWorkList1 = MoreSSAWork ++ SSAWorkList,
          MoreFlowWork = [{Node, Destination} || Destination<-MoreFlowDests],
          scc(MoreFlowWork, SSAWorkList1, Environment1)
      end;
    false ->
      scc([], SSAWorkList, Environment)
  end.

%%-----------------------------------------------------------------------------
%% Procedure : update_cfg/1
%% Purpose   : Transforms the cfg into something more pleasant.
%%             Here the mapping of variables & registers to lattice-values is 
%%             used to actually change the code.
%% Arguments : Environment - in which everything happens.
%% Returns   : A new CFG.
%%-----------------------------------------------------------------------------

update_cfg(Environment) ->
  NodeList = get_nodelist(Environment),
  CFG1     = update_nodes(NodeList, Environment),
  %% why not hipe_???_ssa:remove_dead_code ?
  CFG2     = ?CFG:remove_unreachable_code(CFG1),
  CFG2.

%%-----------------------------------------------------------------------------
%% Procedure : update_nodes/2
%% Purpose   : loop over all nodes in a list of nodes, ignoring any 
%%             non-reachable node.
%% Arguments : NodeList - the list of nodes.
%%             Environment - in which everything happens.
%% Returns   : a new cfg.
%%-----------------------------------------------------------------------------

update_nodes([], Environment) ->
  env__cfg(Environment);
update_nodes([Node | NodeList], Environment) ->
  NewEnvironment =
    case reachable(Node, Environment) of
      true ->
	Instructions = extract_code(Node, Environment),
	Updater = fun(Instruction) ->
		      update_instruction(Instruction, Environment)
		  end,
	NewInstructions = lists:flatmap(Updater, Instructions),
	update_code(Node, NewInstructions, Environment);
      false -> 
	Environment
    end,
  update_nodes(NodeList, NewEnvironment).

%%-----------------------------------------------------------------------------
%% Procedure : update_code/3
%% Purpose   : Insert a list of new instructions into the cfg in the
%%             environment
%% Arguments : Node - name of the bb whose instructions we replace.
%%             NewInstructions - The list of new instructions
%%             Env  - The environment
%% Returns   : A new environment
%%-----------------------------------------------------------------------------

update_code(Node, NewInstructions, Environment) ->
  CFG                 = env__cfg(Environment),
  BB                  = ?CFG:bb(CFG, Node),
  OrderedInstructions = put_phi_nodes_first(NewInstructions),
  NewBB               = hipe_bb:code_update(BB, OrderedInstructions),
  NewCFG              = ?CFG:bb_add(CFG, Node, NewBB),
  Environment#env{cfg = NewCFG}.

%%-----------------------------------------------------------------------------
%% Procedure : put_phi_nodes_first/1
%% Purpose   : Move all phi-instructions to the beginning of the basic block.
%% Arguments : Instructions - The list of instructions
%% Returns   : A list of instructions where the phi-nodes are first.
%%-----------------------------------------------------------------------------

put_phi_nodes_first(Instructions) ->
  {PhiInstructions, OtherInstructions} = 
    partition(fun(X) -> ?CODE:is_phi(X) end, Instructions),
  PhiInstructions ++ OtherInstructions.

%%-----------------------------------------------------------------------------

partition(Function, List) ->
  partition(Function, List, [], []).

partition(_Function, [], True, False) ->
  {lists:reverse(True), lists:reverse(False)};

partition(Function, [Hd | Tail], True, False) ->
  case Function(Hd) of
    true ->
      partition(Function, Tail, [Hd | True], False);
    false ->
      partition(Function, Tail, True, [Hd | False])
  end.

%%-----------------------------------------------------------------------------
%% Procedure : visit_phi_nodes/4
%% Purpose   : visit all the phi-nodes in a bb and return the list of 
%%             remaining instructions, new ssa-edges and a new environment.
%% Arguments : [Inst|Insts] - The list of instructions in the bb
%%             Node - Name of the current node.
%%             Environment - the environment
%%             SSAWork  - the ssawork found so far.
%% Returns   : {Environment, Instruction list, SSAWorkList}
%%-----------------------------------------------------------------------------

visit_phi_nodes([], CurrentNode, _Environment, _SSAWork) ->
  ?EXIT({"~w: visit_phi_nodes/4 Basic block contains no code",
	 ?MODULE, CurrentNode});
visit_phi_nodes(Is = [Inst | Insts], Node, Environment, SSAWork) ->
  case ?CODE:is_phi(Inst) of
    true ->
      {Environment1, NewSSA} = visit_phi(Inst, Node, Environment),
      visit_phi_nodes(Insts, Node, Environment1, NewSSA ++ SSAWork);
    false ->
      {Environment, Is, SSAWork}
  end.

%%-----------------------------------------------------------------------------
%% Procedure : visit_phi/3
%% Purpose   : visit a phi-node
%% Arguments : PhiInstruction- The instruction
%%             CurrentNode - Name of the current node.
%%             Environment - the environment
%% Returns   : {NewEnvironment, SSAWork}
%%-----------------------------------------------------------------------------

visit_phi(PhiInstruction, CurrentNode, Environment) ->
  ArgumentList = ?CODE:phi_arglist(PhiInstruction),
  Value        = get_phi_value(ArgumentList, CurrentNode, Environment, top),
  Name         = ?CODE:phi_dst(PhiInstruction),
  {Environment1, SSAWork} = update_lattice_value({Name, Value}, Environment),
  {Environment1, SSAWork}.

%%-----------------------------------------------------------------------------
%% Procedure : get_phi_value/4
%% Purpose   : compute the result of a phi-function, taking care to ignore 
%%             edges that are not yet executable.
%% Arguments : ArgList - the list of arguments {Node, Value pair}
%%             CurrentNode - the current node
%%             Environment - well...
%%             CurrentValue - the meet of the relevant already processed values
%% Returns   : Integer, top or bottom
%%-----------------------------------------------------------------------------

%% the arglist contains {predecessor, variable} elements.  Remember
%% to be optimistic in this part, hopefully, topvalues will fall down
%% to become constants. Hence topvalues are more or less ignored here.
get_phi_value([], _CurrentNode, _Environment, CurrentValue) ->
  CurrentValue;
get_phi_value([{PredecessorNode, Variable}| ArgList],
              CurrentNode,
              Environment,
              CurrentValue) ->
  case executable({PredecessorNode, CurrentNode}, Environment) of
    true ->
      NewValue = lookup_lattice_value(Variable, Environment),
      case NewValue of
        bottom ->
          bottom;
        top ->
          get_phi_value(ArgList, CurrentNode, Environment, CurrentValue);
        _ ->
          case CurrentValue of
            top -> 
              get_phi_value(ArgList, CurrentNode, Environment, NewValue);
            _ ->
              case (NewValue =:= CurrentValue) of
                true ->
                  get_phi_value(ArgList, CurrentNode, Environment, NewValue);
                false ->  %% two different constants.
                  bottom
              end
          end
      end;
    false ->  %% non-executable transitions don't affect the value.
      get_phi_value(ArgList, CurrentNode, Environment, CurrentValue)
  end.

%%------------------------------ environment ----------------------------------

reachable(Node, Environment) ->
  Predecessors = predecessors(Node, Environment),
  Executable   = fun(Pred) -> executable({Pred, Node}, Environment) end,
  lists:any(Executable, Predecessors).

%%-----------------------------------------------------------------------------

mark_as_executable(Edge, Environment) ->
  ExecutableFlags  = env__executable_flags(Environment),
  ExecutableFlags1 = gb_sets:add(Edge, ExecutableFlags),
  Environment#env{executable_flags = ExecutableFlags1}.

%%-----------------------------------------------------------------------------

mark_as_handled(Node, Environment = #env{handled_blocks=Handled}) ->
  NewHandled = gb_sets:add_element(Node, Handled),
  Environment#env{handled_blocks=NewHandled}.

handled(Node, #env{handled_blocks=Handled}) ->
  gb_sets:is_element(Node, Handled).

%%-----------------------------------------------------------------------------

extract_code(Node, Environment) ->
  CFG = env__cfg(Environment),
  case ?CFG:bb(CFG, Node) of
    not_found -> ?WARNING_MSG("Could not find label ~w.\n", [Node]),
                 [];
    BB -> hipe_bb:code(BB)
  end.

%%-----------------------------------------------------------------------------

predecessors(Node, Environment) ->
  CFG = env__cfg(Environment),
  ?CFG:pred(CFG, Node).

%%-----------------------------------------------------------------------------

executable(Edge, Environment) ->
  ExecutableFlags = env__executable_flags(Environment),
  gb_sets:is_member(Edge, ExecutableFlags).

%%-----------------------------------------------------------------------------

update_lattice_value({[], _NewValue}, Environment) ->
  {Environment, []};
update_lattice_value({Names, NewValue}, Environment) when is_list(Names) ->
  Update = 
    fun(Dst, {Env, SSA}) ->
        {NewEnv, NewSSA} = 
          update_lattice_value({Dst, NewValue}, Env),
        {NewEnv, SSA ++ NewSSA}
    end,
  lists:foldl(Update, {Environment, []}, Names);
%% update_lattice_value({Name, {Res, N, Z, C, V} }, _) ->
%%   ?EXIT({"inserting dumt grejs", {Name, {Res, N, Z, C, V} } });
update_lattice_value({Name, NewValue}, Environment) ->
  LatticeValues = env__lattice_values(Environment),
  {LatticeValues1, SSAWork} = 
    case gb_trees:lookup(Name, LatticeValues) of
      none ->
        {gb_trees:insert(Name, NewValue, LatticeValues),
	 lookup_ssa_edges(Name, Environment)};
      {value, NewValue} ->
	{LatticeValues, []};
      {value, _} ->
        {gb_trees:update(Name, NewValue, LatticeValues),
	 lookup_ssa_edges(Name, Environment)}
    end,
  {Environment#env{lattice_values = LatticeValues1}, SSAWork}.

%%-----------------------------------------------------------------------------

lookup_ssa_edges(Variable, Environment) ->
  SSAEdges = env__ssa_edges(Environment),
  case gb_trees:lookup(Variable, SSAEdges) of
    {value, X} ->
      X;
    _ -> % Unused variable
      []
  end.

%%-----------------------------------------------------------------------------

get_nodelist(Environment) ->
  CFG = env__cfg(Environment),
  ?CFG:labels(CFG).

%%-----------------------------------------------------------------------------

-ifdef(DEBUG).

%%-----------------------------------------------------------------------------
%%---------------------------------- DEBUG ------------------------------------

error(Text) ->
  error(Text, []).

error(Text, Data) ->
  io:format("Internal compiler error in ~w\n",[?MODULE]),
  io:format(Text, Data),
  io:format("\n\n"),
  halt().

%%-----------------------------------------------------------------------------

print_environment(Environment) ->
  io:format("============================================================\n"),
  io:format("Executable flags: "),
  print_executable_flags(env__executable_flags(Environment)),
  io:format("Lattice values --->\n"),
  print_lattice_values(env__lattice_values(Environment)),
  io:format("SSA edges --->\n"),
  print_ssa_edges(env__ssa_edges(Environment)),
  io:format("============================================================\n").
  
%%-----------------------------------------------------------------------------

print_executable_flags(ExecutableFlags) ->
  ListOfFlags = gb_sets:to_list(ExecutableFlags),
  Printer     = fun ({Source, Destination}) -> 
		    io:format("(~w, ~w), ", [Source, Destination]) end,
  lists:foreach(Printer, ListOfFlags),
  io:format("()\n").

%%-----------------------------------------------------------------------------

print_lattice_values(LatticeValues) ->
  ListOfLatticeValues = gb_trees:to_list(LatticeValues),
  Printer             = fun ({Key, Value}) ->
			    io:format("~w = ~w\n", [Key, Value]) end,
  lists:foreach(Printer, ListOfLatticeValues).

%%-----------------------------------------------------------------------------

print_ssa_edges(SSAEdges) ->
  ListOfSSAEdges = gb_trees:to_list(SSAEdges),
  Printer        = fun ({Key, Value}) ->
		       io:format("~w: ~w\n", [Key, Value]) end,
  lists:foreach(Printer, ListOfSSAEdges).

%%-----------------------------------------------------------------------------

-endif.	%% DEBUG

%%-----------------------------------------------------------------------------

